package org.dding.core.manager;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.io.FileUtils;
import org.dding.core.LifecycleBase;
import org.dding.core.exception.LifecycleException;
import org.dding.core.instrument.MscLoader;
import org.dding.core.log.ILogger;
import org.dding.core.log.LoggerFactory;
import org.dding.core.service.ServerConfigService;
import org.dding.core.service.StandaloneServerConfig;
import org.dding.core.util.FileExtUtils;
import org.dding.msc.exception.ServiceInstantiateException;
import org.dding.msc.service.MSCFactory;
import org.dding.msc.service.Service;
import org.dding.msc.service.ServiceContainer;
import org.dding.msc.service.ServiceController;
import org.dding.msc.service.ServiceInfoWarehouse;
import org.dding.msc.service.ServiceInfoWarehouse.ServiceInfo;
import org.dding.msc.service.ServiceName;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleClassLoader;
import org.jboss.modules.ModuleIdentifier;
import org.jboss.modules.ModuleLoadException;
import org.jboss.modules.ResourceLoader;

public class ServiceManager extends LifecycleBase implements IServiceManager {

	private MscLoader mscLoader = MscLoader.getInstence();
	private ILogger logger = LoggerFactory.getLogger(getClass());
	protected ServiceContainer serviceContainer;
	private ServiceInfoWarehouse house = ServiceInfoWarehouse.getInstance();
	private String name;
	private IModuleManager moduleManager;

	private static Map<String, Service<? extends Object>> _system_services = new HashMap<String, Service<? extends Object>>();

	public ServiceManager(String name) {
		this.name = name;
	}

	@Override
	public boolean loadServices(String moduleIdentifer, boolean unzip)
			throws IOException {
		Module module = null;

		module = moduleManager.findModule(moduleIdentifer);

		if (module == null) {
			try {
				module = moduleManager.loadModule(moduleIdentifer, unzip);
			} catch (ModuleLoadException e) {
				logger.error(e, "load service fialure in module "
						+ moduleIdentifer);
				return false;
			}
		}
		File[] files = new File[0];
		if (module == null)
			return false;

		files = findModuleJar(module.getIdentifier(), module);
		mscLoader.readWithASM(files, module.getClassLoaderPrivate());
		if (unzip)
			unzip(files);
		registerServices(module.getIdentifier().toString());
		mscLoader.clear();
		return true;
	}

	@Override
	public boolean unloadServices(String moduleName) {
		return unloadServices(moduleName, true);
	}

	@Override
	public boolean unloadServices(String moduleName, boolean delete) {
		Module module = moduleManager.findModule(moduleName);

		if (module != null) {

			if (!moduleManager.isModuleExist(moduleName)) {
				logger.error("unload service fialure,the module " + moduleName
						+ " is not exist.");
				return false;
			}
		}
		unregisterServices(moduleName);
		if (delete)
			deleteDir(moduleName);
		return true;
	}

	public void registerServices(String moduleIdentifer) {
		Class<?>[] deployServices = mscLoader
				.findClassAnnotatedWith(org.dding.core.annotation.Service.class);
		for (Class<?> ServiceClass : deployServices) {
			if (ServiceClass.isInterface()
					|| !Modifier.isPublic(ServiceClass.getModifiers())
					|| Modifier.isAbstract(ServiceClass.getModifiers())) {
				logger.warn("Class " + ServiceClass.getName()
						+ " is annotated with @"
						+ Service.class.getSimpleName()
						+ ", but not is not a public concrete class, ignored!");
				continue;
			}
			if (!Service.class.isAssignableFrom(ServiceClass)) {
				logger.warn("Class " + ServiceClass.getName()
						+ " is annotated with @"
						+ Service.class.getSimpleName()
						+ ", but not implements interface "
						+ Service.class.getName() + ", ignored!");
				continue;
			}
			try {
				registerService(
						ServiceClass.getAnnotation(
								org.dding.core.annotation.Service.class).name(),
						house.new ServiceInfo(ServiceClass
								.asSubclass(Service.class), moduleIdentifer));

				logger.info("Service "
						+ ServiceClass.getName()
						+ " loaded with id: "
						+ ServiceClass.getAnnotation(
								org.dding.core.annotation.Service.class).name()
						+ ".");
			} catch (ServiceInstantiateException e) {
				logger.warn(
						e,
						"register service "
								+ ServiceClass
										.getAnnotation(
												org.dding.core.annotation.Service.class)
										.name() + " failed.");
			}
		}
	}

	public void unregisterServices(String moduleName) {
		List<String> services = house.getServiceByModule(moduleName);
		if (services == null) {
			logger.info("module " + moduleName + " has not services.");
			return;
		}
		for (String service : services) {
			serviceContainer.stopService(service);
			serviceContainer.removeService(service);
			house.remove(service);
			logger.debug("unregister service " + service + " in module "
					+ moduleName);
		}
		house.clear(moduleName);
	}

	private void registerService(String name, ServiceInfo serviceInfo)
			throws ServiceInstantiateException {
		try {
			house.registry(name, serviceInfo);
			Service<?> service = serviceInfo.serviceClass.getConstructor()
					.newInstance();
			serviceContainer.addService(ServiceName.of(name), service)
					.install();
			logger.debug("register service " + service + ".");
		} catch (Throwable e) {
			throw new ServiceInstantiateException("Component " + name
					+ " Instantiate failed with exception ", e);
		}
	}

	@Override
	public ServiceController<?> getService(String serviceName) {
		ServiceController<?> controller = serviceContainer
				.getService(ServiceName.of(serviceName));
		if (controller == null) {
			logger.warn("can't find service " + serviceName);
			return null;
		}
		return controller;
	}

	public File[] findModuleJar(ModuleIdentifier moduleIdentifier, Module module) {

		final ModuleClassLoader classLoader = module.getClassLoaderPrivate();
		final ResourceLoader[] loaders = classLoader.getResourceLoaders();
		List<File> files = new ArrayList<File>();
		for (ResourceLoader loader : loaders) {
			String moduleUrl = moduleManager.getModulePath() + File.separator
					+ FileExtUtils.toPath(moduleIdentifier.getName())
					+ File.separator + moduleIdentifier.getSlot()
					+ File.separator + loader.getRootName();
			File file = new File(moduleUrl);
			if (file.isDirectory())
				continue;
			files.add(file);
		}
		return files.toArray(new File[0]);
	}

	private void unzip(File[] files) throws IOException {
		for (File file : files) {
			File destation = new File(file.getParentFile(), file.getName()
					.substring(0, file.getName().indexOf(".")));
			if (file.exists() && !destation.exists()) {
				FileExtUtils.extractJar(file, destation);
			}
		}
	}

	private void deleteDir(String name) {
		String moduleUrl = moduleManager.getModulePath()
				+ File.separator
				+ FileExtUtils.toPath(ModuleIdentifier.fromString(name)
						.getName()) + File.separator
				+ ModuleIdentifier.fromString(name).getSlot() + File.separator;
		File[] dirs = new File(moduleUrl).listFiles(new FileFilter() {

			@Override
			public boolean accept(File pathname) {
				if (pathname.isDirectory())
					return true;
				return false;
			}
		});
		for (File file : dirs) {
			try {
				FileUtils.deleteDirectory(file);
			} catch (IOException e) {
				logger.warn(e, "delete dir " + file + " failure.");
			}
		}
	}

	@Override
	public List<ServiceName> getServiceNames() {
		return serviceContainer.getServiceNames();
	}

	@Override
	public boolean stopService(String serviceName) {
		return serviceContainer.stopService(serviceName);
	}

	@Override
	public boolean startService(String serviceName) {
		return serviceContainer.startService(serviceName);
	}

	@Override
	public boolean removeService(String serviceName) {
		if (serviceContainer.removeService(serviceName))
			house.remove(serviceName);
		return true;
	}

	@Override
	public boolean waitService(String serviceName) {
		return serviceContainer.waitService(serviceName);
	}

	@Override
	public void setModuleManager(IModuleManager moduleManager) {
		this.moduleManager = moduleManager;
		fillSystemService();
	}

	@Override
	protected void initInternal() throws LifecycleException {
		logger.debug("init service container");
		serviceContainer = MSCFactory.create(name);
	}

	@Override
	protected void startInternal() throws LifecycleException {

		for (Entry<String, Service<? extends Object>> entry : _system_services
				.entrySet()) {
			house.registry(entry.getKey(), house.new ServiceInfo(entry
					.getValue().getClass(), _SYSTEM_SERVICE + "system.module"));
			serviceContainer.addService(ServiceName.of(entry.getKey()),
					entry.getValue()).install();
		}
	}

	@Override
	protected void stopInternal() throws LifecycleException {

		for (String key : _system_services.keySet()) {

			serviceContainer.stopService(key);
			serviceContainer.removeService(key);
			house.remove(key);
		}
	}

	@Override
	public IModuleManager getModuleManager() {
		return moduleManager;
	}

	@Override
	public String getName() {
		return name;
	}

	@Override
	public boolean isServiceExist(String serviceName) {
		ServiceInfo serviceInfo = ServiceInfoWarehouse.getInstance().get(
				serviceName);
		if (serviceInfo != null)
			return true;
		return false;
	}

	private void fillSystemService() {
		_system_services.put(_SYSTEM_SERVICE + "server.config",
				new ServerConfigService(new StandaloneServerConfig(
						moduleManager)));
	}
}
